/********************************************************************/
/*          MultiSio.c                                              */
/*            Multi-play Communication Library                      */
/*    (Timer Interrupt Send & Communication Data Synchronization)   */
/*            (Multiple Interrupts)                                 */
/*                                                                  */
/*          Copyright (C) 1999-2001 NINTENDO Co.,Ltd.               */
/********************************************************************/
//#define CODE32
#include "MultiSio.h"


/*-------------------- Global Variable  ----------------------------*/

MultiSioArea     Ms;            // Multi-play Communication Work Area

#ifdef MULTI_SIO_DI_FUNC_FAST
u32 RecvFuncBuf[0x40/4];        // Receive Data/Check Buffer Change Routine RAM Execution Buffer
u32 IntrFuncBuf[0x100/4];       // Interrupt Routine RAM Execution Buffer
#endif

static const u8 MultiSioLib_Var[]="MultiSioSync010214";


/*------------------------------------------------------------------*/
/*                      Multi-play Communication Initialization     */
/*------------------------------------------------------------------*/
extern u32 MultiSioRecvBufChange(void);

void MultiSioInit(void)
{
    int     i;

    *(vu16 *)REG_IME = 0;
    *(vu16 *)REG_IE &= ~(SIO_INTR_FLAG                  // Disable SIO & Timer Interrupt 
                       | MULTI_SIO_TIMER_INTR_FLAG);
    *(vu16 *)REG_IME = 1;

    *(vu16 *)REG_RCNT  = R_SIO_MASTER_MODE;
    *(vu16 *)REG_SIOCNT  = SIO_MULTI_MODE;
    *(vu16 *)REG_SIOCNT |= SIO_IF_ENABLE | MULTI_SIO_BAUD_RATE_NO;

    CpuClear(0, &Ms, sizeof(Ms), 32);                   // Clear Multi-play Communication Work Area

#ifdef MULTI_SIO_DI_FUNC_FAST                           // Copy Function
    CpuCopy(MultiSioRecvBufChange, RecvFuncBuf, sizeof(RecvFuncBuf), 32);
    CpuCopy(MultiSioIntr,          IntrFuncBuf, sizeof(IntrFuncBuf), 32);
#endif

    Ms.BufCounter = sizeof(MultiSioPacket)/2;           // Initialize Buffer Counter

    Ms.CurrentRecvBufp = (u16 *)&Ms.RecvBuf[0][0];      // Set Receive Buffer Pointer
    Ms.LastRecvBufp    = (u16 *)&Ms.RecvBuf[1][0];
    Ms.RecvCheckBufp   = (u16 *)&Ms.RecvBuf[2][0];

    *(vu16 *)REG_IME = 0;
    *(vu16 *)REG_IE |= SIO_INTR_FLAG;                   // Enable SIO Interrupt
    *(vu16 *)REG_IME = 1;
}


/*------------------------------------------------------------------*/
/*                      Start Multi-play Communication              */
/*------------------------------------------------------------------*/

void MultiSioStart(void)
{
    if (Ms.Type)    Ms.StartFlag = 1;                   // Set Start Flag
}

/*------------------------------------------------------------------*/
/*                      Stop Multi-play Communication                   */
/*------------------------------------------------------------------*/

void MultiSioStop(void)
{
    *(vu16 *)REG_IME = 0;
    *(vu16 *)REG_IE &= ~(SIO_INTR_FLAG                  // Disable SIO & Timer Interrupt
                       | MULTI_SIO_TIMER_INTR_FLAG);
    *(vu16 *)REG_IME = 1;

    *(vu16 *)REG_SIOCNT = SIO_MULTI_MODE                // Stop SIO
                        | MULTI_SIO_BAUD_RATE_NO;
    *(vu32 *)REG_MULTI_SIO_TIMER                        // Stop Timer
                        = MULTI_SIO_TIMER_COUNT;

    *(vu16 *)REG_IF = SIO_INTR_FLAG                     // Reset IF
                    | MULTI_SIO_TIMER_INTR_FLAG;

    Ms.StartFlag = 0;                                   // Reset Start Flag
}


/*------------------------------------------------------------------*/
/*                      Multi-play Communication Main               */
/*------------------------------------------------------------------*/

u32 MultiSioMain(void *Sendp, void *Recvp)
{
    SioMultiCnt     SioCntBak;
    int             i, ii;

    switch (Ms.State) {
        case 0: SioCntBak = *(SioMultiCnt *)REG_SIOCNT;
                if (!SioCntBak.ID) {                        // Check Connection
                    if (!SioCntBak.SD || SioCntBak.Enable)    break;
                    if (!SioCntBak.SI && Ms.BufCounter == sizeof(MultiSioPacket)/2) {
                        *(vu16 *)REG_IME = 0;
                        *(vu16 *)REG_IE &= ~SIO_INTR_FLAG;              // Disable SIO Interrupt
                        *(vu16 *)REG_IE |=  MULTI_SIO_TIMER_INTR_FLAG;  // Enable Timer Interrupt
                        *(vu16 *)REG_IME = 1;

                        ((SioMultiCnt *)REG_SIOCNT)->IF_Enable = 0;     // Reset SIO-IFE
                        *(vu16 *)REG_IF  =  SIO_INTR_FLAG               // Reset IF
                                         |  MULTI_SIO_TIMER_INTR_FLAG;

                        *(vu32 *)REG_MULTI_SIO_TIMER                    // Timer Initialization
                                         = MULTI_SIO_TIMER_COUNT;

                        Ms.Type = SIO_MULTI_PARENT;
                    }
                }
                Ms.State = 1;
        case 1: MultiSioSendDataSet(Sendp);             // Set Send Data
                MultiSioRecvDataCheck(Recvp);           // Check Receive Data
                break;
    }

    Ms.SendFrameCounter++;

    return      Ms.RecvSuccessFlags
              | (Ms.Type == SIO_MULTI_PARENT) << 7
              | Ms.ConnectedFlags << 8;
}


/*------------------------------------------------------------------*/
/*                      Set Send Data                               */
/*------------------------------------------------------------------*/

void MultiSioSendDataSet(void *Sendp)
{
    s32     CheckSum = 0;
    int     i;

    Ms.SendBuf.FrameCounter = (u8 )Ms.SendFrameCounter;
    Ms.SendBuf.RecvErrorFlags =  Ms.ConnectedFlags ^ Ms.RecvSuccessFlags;
    Ms.SendBuf.CheckSum = 0;

    CpuCopy(Sendp, Ms.SendBuf.Data, MULTI_SIO_BLOCK_SIZE, 32);  // Set Send Data

    for (i=0; i<sizeof(MultiSioPacket)/2 - 2; i++)      // Calculate Checksum Send data
        CheckSum += ((u16 *)&Ms.SendBuf)[i];
    Ms.SendBuf.CheckSum = ~CheckSum;
}

/*------------------------------------------------------------------*/
/*                     Check Receive Data                           */
/*------------------------------------------------------------------*/

u32 MultiSioRecvDataCheck(void *Recvp)
{
    u32 (*MultiSioRecvBufChangeOnRam)(void) = (u32 (*)(void))RecvFuncBuf;
    s32      CheckSum;
    vu32     RecvCheck = 0;
    u8       SyncRecvFlagBak;
    u8       CounterDiff;
    u16     *BufpTmp;
    int      i, ii;


#ifdef MULTI_SIO_DI_FUNC_FAST                           // Update Receive Data/Check Buffer
    SyncRecvFlagBak = MultiSioRecvBufChangeOnRam();
#else
    *(vu16 *)REG_IME = 0;                               // Disable Interrupts (Approx. 30 Clocks)

    BufpTmp = Ms.RecvCheckBufp;                         // Update Receive Data/Check Buffer
    Ms.RecvCheckBufp = Ms.LastRecvBufp;
    Ms.LastRecvBufp = BufpTmp;

    SyncRecvFlagBak = Ms.SyncRecvFlag;                  // Copy Receive Confirmation Flag
    Ms.SyncRecvFlag = 0;

    *(vu16 *)REG_IME = 1;                               // Enable Interrupt
#endif

    Ms.RecvSuccessFlags = 0;

    if (SyncRecvFlagBak) {                              // Receive Success Confirmation
        for (i=0; i<MULTI_SIO_PLAYERS_MAX; i++) {
            BufpTmp = Ms.RecvCheckBufp + i * sizeof(MultiSioPacket)/2;
            CheckSum = 0;                               // Calculate Checksum Receive Data
            for (ii=0; ii<sizeof(MultiSioPacket)/2 - 2; ii++)
                CheckSum +=  BufpTmp[ii];

            if ((s16 )CheckSum == -1) {
                CpuCopy(&((u8 *)BufpTmp)[4], &((u8 *)Recvp)[i*MULTI_SIO_BLOCK_SIZE],
                        MULTI_SIO_BLOCK_SIZE, 32);
                Ms.RecvSuccessFlags |= 1 << i;
            }

            CpuClear(0, &((u8 *)BufpTmp)[4], MULTI_SIO_BLOCK_SIZE, 32);
        }
    }

    Ms.ConnectedFlags |= Ms.RecvSuccessFlags;           // Set Connect End Flag

    return Ms.RecvSuccessFlags;
}


/*==================================================================*/
/*     Multi-play Communication V-Blank Interrupt Processing        */
/*==================================================================*/

void MultiSioVSync(void)
{
    if (Ms.State && Ms.StartFlag) {
        ((SioMultiCnt *)REG_SIOCNT)->Data = MULTI_SIO_SYNC_DATA;
                                                        // Set Synchronized Data

        *(vu16 *)REG_SIOCNT |= SIO_ENABLE;              // Start Communication
        *(vu16 *)REG_MULTI_SIO_TIMER_H                  // Start Timer 
                             = (TMR_PRESCALER_1CK
                              | TMR_IF_ENABLE | TMR_ENABLE) >> 16;
    }
}


/*==================================================================*/
/*                  Multi-play Communication Interrupt Routine      */
/*==================================================================*/

void MultiSioIntr(void)
{
    u16      RecvTmp[4];
    u16     *BufpTmp;
    int     i, ii;


    // Save Receive Data

    *(u64 *)RecvTmp = *(u64 *)REG_SIOMLT_RECV;


    // Receive Data Processing


    if (RecvTmp[0] == MULTI_SIO_SYNC_DATA
     && Ms.BufCounter > (s32 )(sizeof(MultiSioPacket)/2 - 3)) {
        Ms.BufCounter = -(MULTI_SIO_DUMMY_SEND_TIMES + 2);

        *(vu16 *)REG_IME = 0;
        *(vu16 *)INTR_CHECK_BUF |= SIO_INTR_FLAG;       // Set SIO Interrupt Check Flag
        *(vu16 *)REG_IME = 1;
    } else if (Ms.BufCounter < 0) {
    } else {
        for (i=0; i<MULTI_SIO_PLAYERS_MAX; i++)
            (Ms.CurrentRecvBufp + i * sizeof(MultiSioPacket)/2)[Ms.BufCounter] = RecvTmp[i];
                                                        // Store Receive Data
        if (Ms.BufCounter == (s32 )(sizeof(MultiSioPacket)/2 - 3)) {
            BufpTmp = Ms.LastRecvBufp;                  // Change Receive Buffer
            Ms.LastRecvBufp = Ms.CurrentRecvBufp;
            Ms.CurrentRecvBufp = BufpTmp;
            Ms.SyncRecvFlag = 1;                        // Receive Completion
        }
    }
    if (Ms.BufCounter < (s32 )(sizeof(MultiSioPacket)/2 - 1))   Ms.BufCounter++;


    // Send Data Processing

    if (Ms.Type)
        *(vu16 *)REG_MULTI_SIO_TIMER_H = 0;             // Stop Timer 

    if (Ms.BufCounter <= (s32 )(sizeof(MultiSioPacket)/2 - 3)) {
        if (Ms.BufCounter >= 0)
            ((SioMultiCnt *)REG_SIOCNT)->Data = ((u16 *)&Ms.SendBuf)[Ms.BufCounter];
                                                        // Set Send Data
        if (Ms.Type) {
            *(vu16 *)REG_SIOCNT |= SIO_ENABLE;          // Restart Send
            *(vu16 *)REG_MULTI_SIO_TIMER_H              // Restart Timer 
                                 = (TMR_PRESCALER_1CK
                                  | TMR_IF_ENABLE | TMR_ENABLE) >> 16;
        }
    }

}


